#include "kcp.h"
#include "Util.h"
#include <chrono>
#include "kcpagent.h"
#include "easylogging++.h"


void KCP::Send(shared_ptr<std::vector<unsigned char> > data){
	LOG(INFO) << "KCPSend:" << sendQueue.size();
	auto pack = shared_ptr<KCPPacket>(new KCPPacket());
	pack->data = data;
	sendQueue.push(pack);
}

 shared_ptr< KCPPacket > KCP::Recv() {
 	LOG(INFO) << "recvBuf:" << recvBuf.size();
 	if(recvBuf.size() > 0) {
 		auto pack = recvBuf.front();
 		recvBuf.pop();
 		return pack;
 	}
 	return shared_ptr< KCPPacket >();
 }

 void KCP::Input(shared_ptr< std::vector<unsigned char> > data) {
 	LOG(INFO) << "Input:" << data->size()<<":"<<recvQueue.size();

 	auto pack = shared_ptr<KCPPacket> (new KCPPacket());
 	pack->DecodeData(*data);
 	recvQueue.push(pack);
 }

void KCP::Update(double delta) {
	accTime += delta;
	LOG(INFO) << "accTime:" << accTime << ":" << delta;
	if(accTime >= interval && !IsClose) {
		//accTime -= interval;
		accTime = 0;//每次会清理掉所有的报文
		HandleRecv();
		HandleAcked();
		HandleSend();
	}
}

void KCP::PutPackInWindow() {
	while(sendBuf.size() < windSz && sendQueue.size() > 0) {
		auto newSn = sendId;
		auto inWin = CheckInWin(newSn, winStart, winEnd);
		if(inWin) {
			auto seg = sendQueue.front();
			sendQueue.pop();
			sendBuf.push_back(seg);
			seg->cmd = KCPPacketCMD::CMD_PUSH;
			seg->ackTimeout = 1;
			seg->sn = sendId++;
		}else {
			break;
		}
	}
}

void KCP::CloseKCP() {
	IsClose = true;
}

void KCP::Close() {
	IsClose = true;
	closeEventHandler();
}

void KCP::SendWin() {
	for(auto it = sendBuf.begin(); it != sendBuf.end();) {
		auto seg = (*it);
		it++;
		if(seg->cmd == KCPPacketCMD::CMD_PUSH) {
			seg->cmd = KCPPacketCMD::CMD_WAITACK;
			seg->EncodeFull();
			seg->sendTime = GetNow();
			outputFunc(seg);
		}else if(seg->cmd == KCPPacketCMD::CMD_WAITACK) {
			if(seg->ackTimeout <= 0 ) {
				seg->ackTimeout = 1;
				auto d = GetNow();
				//超时1s则不要重传了直接通知上层关闭
				if(d-seg->sendTime > 1) {
					Close();
				}else {
					outputFunc(seg);
				}
			}else {
				seg->ackTimeout--;
			}
		}
	}
}

void KCP::HandleAcked() {
	for(auto it = sendBuf.begin(); it != sendBuf.end();) {
		auto seg = (*it);
		auto cur = it++;
		if(seg->cmd == KCPPacketCMD::CMD_ACKED) {
			if(cur == sendBuf.begin()) {
				winStart++;
				winEnd++;
			}
			sendBuf.erase(cur);
		}
	}
}

void KCP::HandleSend(){
	PutPackInWindow();
	SendWin();
}

bool KCP::CheckInWin(uint32_t id, uint32_t st, uint32_t end) {
	auto inWin = false;
	if(st > end) {
		inWin = id >= st || id < end;
	}else {
		inWin = id >= st && id < end;
	}
	return inWin;
}

void KCP::CheckLoopList() {
	while(true) {
		auto pack = recvList->TakePacket();
		if(pack != nullptr) {
			recvBuf.push(pack);
		}else {
			break;
		}
	}
}
void KCP::outputFunc(shared_ptr<KCPPacket> pa) {
	kcpAgent->SendPacket(pa);
}

void KCP::closeEventHandler() {
	kcpAgent->KCPClosed();
}

void KCP::HandleRecv() {
	auto c = 0;
	while(c <= maxRecvNum) {
		shared_ptr<KCPPacket> recPack;
		if(!recvQueue.empty()) {
			recPack = recvQueue.front();
			recvQueue.pop();
		}
		if(recPack != nullptr) {
			c++;
			auto seg = recPack;

			LOG(INFO) << "HandleRecv:" << seg->isAck << seg->sn;
			if(seg->isAck) {
				for(auto it = sendBuf.begin(); it != sendBuf.end(); it++) {
					if((*it)->sn == seg->sn) {
						(*it)->cmd = KCPPacketCMD::CMD_ACKED;
						break;
					}
				}
			}else {
				auto srvSn = seg->sn;
				auto ret = CheckInWin(srvSn, ackWinStart, ackWinEnd);
				if(ret) {
					auto sendAck = shared_ptr<KCPPacket>(new KCPPacket());
					sendAck->isAck = true;
					sendAck->sn = seg->sn;
					sendAck->EncodeFull();
					outputFunc(sendAck);
					auto tailHalfSt = ackWinStart +windSz;
					auto tailhalfEnd = ackWinEnd;
					if(CheckInWin(srvSn, tailHalfSt, tailhalfEnd)) {
						LOG(INFO) << "MoveStart";
						ackWinStart++;
						ackWinEnd++;
						ackYet.erase(ackWinStart-1);
						recvList->MoveStart();
					}

					if(ackYet.find(srvSn) == ackYet.end()) {
						LOG(INFO) << "CheckLoopList";
						ackYet.insert(srvSn);
						recvList->AddPacket(seg, srvSn-ackWinStart);
						CheckLoopList();
					}
				}
			}
		}else {
			break;
		}
	}
}